<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>

    <script type="text/javascript" src="jquery.js"></script>

    <script type="text/javascript" src="../dist/jmass.js"></script>

    <script type="text/javascript">
        bench = {};
        bench.startDate = null;
        bench.endDate = null;
        bench.diff = 0;
        bench.diffStr = "";
        bench.start = function() {
            bench.startDate = new Date();
        };

        bench.stop = function() {
            bench.endDate = new Date();
            bench.diff = this.endDate.getTime() - this.startDate.getTime();
            bench.diffStr = bench.diff;
        };
    </script>

    <style type="text/css">
        .test-result { padding: 5px; font-family: Tahoma, Verdana, Arial; font-size: 13px; background-color: #FAFAFA; margin: 5px; }
        .test-result th { font-weight: bold; background-color: blue; color: White; }
        .test-result td { font-weight: normal; background-color: white; padding: 5px; }
        .ok { color: Green; }
        .error { color: Red; font-weight: bold; }
    </style>

    <script type="text/javascript">
        jMass.init();
        $(document).ready(function() {
            var vp = $("#bench");

            var compare = function(a, b) {
                var res = "";
                var ok = true;
                for (var i = 0, il = Math.min(a.length, b.length); i < il; i++) {
                    if (res.length > 0)
                        res = res + ',';
                    if (a[i] === b[i]) {
                        res += '<span class="ok">' + a[i] + '</span>';
                    } else {
                        res += '<span class="error">' + a[i] + '</span>';
                        ok = false;
                    }
                }
                res = '[' + res + ']';
                if (a.length !== b.length) {
                    ok = false;
                    res = '<span class="error">Diferent lengths</span>&nbsp;&nbsp;' + res;
                }
                if (ok) {
                    return '<span class="ok">success</span>';
                }
                else {
                    return '<span class="error">(fail)</span>&nbsp;&nbsp;' + res;
                }
            };

            var sourceArray = [1, 2, 3, 4, 5];

            var test = function(options) {
                if (!options.source)
                    options.source = sourceArray;
                var resStr = "";
                var statusStr = "";
                var resArray = null;
                var idi = jMass.guid;
                bench.start();

                var res = options.query(options.raw ? options.source : options.source.toEnum());
                if (res == null) {
                    resStr = "null";
                }
                else if (res.isEnum) {
                    resArray = res.toArray();
                }
                else if (res.constructor == Array)
                    resArray = res;

                bench.stop();
                var idc = jMass.guid - idi;

                if (options.requires === "@result")
                    options.requires = resArray ? resArray : res;

                var sourceStr = options.source.join(',');
                var requireStr = options.requires === null ? "null" :
                    options.requires.constructor == Array ? options.requires.join(',') : options.requires.toString();

                if (resArray != null) {
                    resStr = resArray.join(',');
                    if (options.requires && options.requires.constructor == Array) {
                        statusStr = compare(resArray, options.requires);
                    } else {
                        statusStr = '<span class="error">Returns not expected array</span>';
                    }
                }
                else {
                    resStr = res ? res.toString() : resStr;
                    var ok = res == options.requires;
                    statusStr = ok ? '<span class="ok">success</span>' : '<span class="error">fail</span>';
                }

                statusStr += "<div>objects: " + idc + "</div>";

                var html = '<table width="100%"><tr><th>Query</th><th>Source array</th><th>Result</th><th>Expect</th><th>Status</th><th>Cost (ms)</tr>';
                html += '<tr><td>' + options.query.toString().replace(/\n/g, '<br/>').replace(/\t/g, "&nbsp;&nbsp;&nbsp;").replace(/\s/g, "&nbsp;") + '</td><td>' + sourceStr + '</td><td>' + resStr + '</td><td>' + requireStr + '</td><td>' + statusStr + '</td><td align="right">' + bench.diffStr + '</td></tr></table>';

                var container = $('<div class="test-result"></div>');
                vp.append(container);
                container.append("<h2>" + options.title + "</h2>");
                container.append(html);
            };


            test({ title: "select",
                source: sourceArray,
                query: function(e) { return e.select(); },
                requires: sourceArray
            });

            test({ title: "selectMany",
                query: function(e) {
                    return e.selectMany(function(x) {
                        return [x, x];
                    });
                },
                requires: [1, 1, 2, 2, 3, 3, 4, 4, 5, 5]
            });

            test({ title: "Contains - no params",
                query: function(e) { return e.contains(); },
                requires: true
            });

            test({ title: "Contains(2) - function",
                query: function(e) { return e.contains(function(x) { return x == 2 }); },
                requires: true
            });

            test({ title: "Contains(10) - function",
                query: function(e) { return e.contains(function(x) { return x == 10 }); },
                requires: false
            });

            test({ title: "toArray()",
                query: function(e) { return e.toArray(); },
                requires: sourceArray
            });

            test({ title: "take(2)",
                query: function(e) { return e.take(2); },
                requires: [1, 2]
            });

            test({ title: "takeWhile (x < 4)",
                query: function(e) { return e.takeWhile(function(x) { return x < 4; }); },
                requires: [1, 2, 3]
            });

            test({ title: "first - no params",
                query: function(e) { return e.first(); },
                requires: 1
            });

            test({ title: "first - function (x > 2)",
                query: function(e) { return e.first(function(x) { return x > 2; }); },
                requires: 3
            });

            test({ title: "firstOrDefault - function (x > 10)",
                query: function(e) { return e.firstOrDefault(function(x) { return x > 10; }, 10); },
                requires: 10
            });

            test({ title: "except - [2,3]",
                query: function(e) { return e.except([2, 3]); },
                requires: [1, 4, 5]
            });

            test({ title: "last - no params",
                query: function(e) { return e.last(); },
                requires: 5
            });

            test({ title: "last - function (x < 4)",
                query: function(e) { return e.last(function(x) { return x < 4; }); },
                requires: 3
            });

            test({ title: "lastOrDefault - function (x > 5)",
                query: function(e) { return e.lastOrDefault(function(x) { return x > 5; }, 100); },
                requires: 100
            });

            test({ title: "sum - no params",
                query: function(e) { return e.sum(); },
                requires: 15
            });

            test({ title: "sum - function (x * 2)",
                query: function(e) { return e.sum(function(x) { return x * 2; }); },
                requires: 30
            });

            test({ title: "average - no params",
                query: function(e) { return e.avg(); },
                requires: 3
            });

            test({ title: "average - function (x * 3)",
                query: function(e) { return e.avg(function(x) { return x * 3; }); },
                requires: 9
            });


            test({ title: "max - no params",
                query: function(e) { return e.max(); },
                requires: 5
            });

            test({ title: "max - function (x % 4)",
                query: function(e) { return e.max(function(x) { return x % 4; }); },
                requires: 3
            });

            test({ title: "min - no params",
                query: function(e) { return e.min(); },
                requires: 1
            });

            test({ title: "min - function (x * 2)",
                query: function(e) { return e.min(function(x) { return x * 2; }); },
                requires: 2
            });

            test({ title: "union - [5,6,7,8]",
                query: function(e) { return e.union([5, 6, 7, 8]); },
                requires: [1, 2, 3, 4, 5, 6, 7, 8]
            });

            test({ title: "union - [5,6,7,8]",
                query: function(e) { return e.union([5, 6, 7, 8]); },
                requires: [1, 2, 3, 4, 5, 6, 7, 8]
            });

            test({ title: "concat - [5,6,7,8]",
                query: function(e) { return e.concat([5, 6, 7, 8]); },
                requires: [1, 2, 3, 4, 5, 5, 6, 7, 8]
            });

            test({ title: "distinct of [1,2,2,3]",
                source: [1, 2, 2, 3],
                query: function(e) { return e.distinct(); },
                requires: [1, 2, 3]
            });

            test({ title: "count - no params",
                query: function(e) { return e.count(); },
                requires: 5
            });

            test({ title: "count - function (x < 3)",
                query: function(e) { return e.count(function(x) { return x < 3; }); },
                requires: 2
            });

            test({ title: "skip(2))",
                query: function(e) { return e.skip(2); },
                requires: [3, 4, 5]
            });

            test({ title: "skipWhile - function(x < 3))",
                query: function(e) { return e.skipWhile(function(x) { return x < 3; }); },
                requires: [4, 5]
            });


            test({ title: "groupBy",
                source: [{ cat: 1, name: "marcelo" },
                          { cat: 2, name: "santos" },
                          { cat: 1, name: "alice" },
                          { cat: 2, name: "sabba" },
                          { cat: 3, name: "none"}],
                query: function(e) {
                    return e.groupBy("x=>x.cat").select(function(x) {
                        return 'key: ' + x.key + ' items: ' + x.items.toEnum().select('y=>y.name').toArray();
                    });
                },
                requires: "@result"
            });

            var gsource = []
            for (var i = 0; i < 2000; i++) {
                gsource[i] = { cat: i % 300, name: 'item' + i };
            }


            test({ title: "groupBy - 2000 items in 300 cats",
                source: [],
                query: function(e) {
                    return gsource.toEnum().groupBy("x=>x.cat").select(function(x) {
                        return 'key: ' + x.key + ' items: ' + x.items.toEnum().select('y=>y.name').toArray();
                    }).count();
                },
                requires: 300
            });

            test({ title: "join",
                source: [{ id: 1, name: 'marcelo' }, { id: 2, name: 'alice' }, { id: 3, name: 'joao'}],
                query: function(e) {
                    var second = [{ id: 1, last: 'santos' }, { id: 2, last: 'sabba' }, { id: 1, last: 'dezem'}];
                    return e.join(second, "x,y => x.id === y.id", "x,y => x.name + ' ' + y.last");
                },
                requires: ["marcelo santos", "marcelo dezem", "alice sabba"]
            });

            test({ title: "left join",
                source: [{ id: 1, name: 'marcelo' }, { id: 2, name: 'alice' }, { id: 3, name: 'joao'}],
                query: function(e) {
                    var second = [{ id: 1, last: 'santos' }, { id: 2, last: 'sabba' }, { id: 1, last: 'dezem'}];
                    return e.leftJoin(second, "x,y => x.id === y.id", "x,y => x.name + ' ' + (y ? y.last : '?') ");
                },
                requires: ["marcelo santos", "marcelo dezem", "alice sabba", "joao ?"]
            });


            test({ title: "Complex 1 - selectMany generates 100 elements and count then",
                query: function(e) {
                    return e.selectMany(function(x) {
                        //generate an array with 20 elements
                        var res = [];
                        for (var i = 0; i < 20; i++)
                            res[i] = i * i;
                        return res;
                    }).count();
                },
                requires: "@result"
            });

            test({ title: "Complex 2 - selectMany generates 10000 elements and take from 8000 to 8010",
                query: function(e) {
                    return e.selectMany(function(x) {
                        //generate an array with 20 elements
                        var res = [];
                        for (var i = 0; i < 2000; i++)
                            res[i] = x + i;
                        return res;
                    }).skip(8000).take(10)
                },
                requires: "@result"
            });

            test({ title: "Complex 3 - concat with self",
                query: function(e) {
                    return e.concat(e);
                },
                requires: [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
            });

            var cnt = 0;

            test({ title: "Complex 4 - generates 5000 elements then select distinct",
                query: function(e) {
                    return e.selectMany(function(x) {
                        var res = [];
                        for (var i = 0; i < 1000; i++)
                            res[i] = cnt < 5 ? cnt++ : (cnt = 0);
                        return res;
                    }).distinct();
                },
                requires: [0, 1, 2, 3, 4]
            });

            test({ title: "Complex 5 - generates 5000 elements then select distinct and count",
                query: function(e) {
                    return e.selectMany(function(x) {
                        var res = [];
                        for (var i = 0; i < 1000; i++)
                            res[i] = Math.ceil(i / x);
                        return res;
                    }).distinct().count();
                },
                requires: 1000
            });

            test({ title: "Complex 6 - selectMany generates 20000 elements and take from 8001 to 15000 then count",
                query: function(e) {
                    return e.selectMany(function(x) {
                        //generate an array with 20 elements
                        var res = [];
                        for (var i = 0; i < 4000; i++)
                            res[i] = x + i;
                        return res;
                    }).skip(8000).take(7000).count()
                },
                requires: 7000
            });

            test({ title: "Complex 7 - using same enum",
                query: function(e) {
                    //concat e ^ 2
                    return e.concat(e.select(function(x) { return x * x }));
                },
                requires: [1, 2, 3, 4, 5, 1, 4, 9, 16, 25]
            });

            test({ title: "Complex 8.1 - using same enum",
                query: function(e) {
                    return e
                        .concat(e.select(function(x) { return x * x; }))
                        .concat(e.select(function(x) { return x / 2; }));
                },
                requires: [1, 2, 3, 4, 5, 1, 4, 9, 16, 25, 0.5, 1, 1.5, 2, 2.5]
            });

            test({ title: "Complex 8.2 - using same enum",
                query: function(e) {
                    return e.concat(e).concat(e).concat(e).concat(e);
                },
                requires: [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
            });

            test({ title: "Complex 9 - many operations",
                query: function(e) {
                    return e
                        .concat(e)
                        .concat(e)
                        .skip(5)
                        .take(10)
                        .where(function(x) { return x % 2 === 0; })
                        .select(function(x) { return x * x; });
                },
                requires: [4, 16, 4, 16]
            });

            test({ title: "Complex 9.1 - many operations (using expressions)",
                query: function(e) {
                    return e
                        .concat(e)
                        .concat(e)
                        .skip(5)
                        .take(10)
                        .where("x => x % 2 === 0")
                        .select("x => x * x");
                },
                requires: [4, 16, 4, 16]
            });


            test({ title: "Complex 10 - expressions",
                query: function(e) {
                    return e
                        .select("x => { x: x, double: x * 2, half: x / 2 }")
                        .select("x => 'value: ' + x.x + '  double: ' + x.double + '  half: ' + x.half");
                },
                requires: "@result"
            });

            test({ title: "Complex 10 - expressions - many operations",
                query: function(e) {
                    return e
                        .select("x => [x,x*2,x*3,x*4,x*5,x*6,x*7,x*8,x*9,x*10]")
                        .selectMany("x => var en = x.toEnum(); return en.concat(en).concat(en).concat(en) ")
                        .count();
                },
                requires: "@result"
            });

            test({ title: "Complex 10.1 - expressions - many operations - big result",
                query: function(e) {
                    var res = e
                        .select("x => [x].toEnum()")
                        .select("x => x.concat(x)")
                        .select("x => x.concat(x)")
                        .select("x => x.concat(x)")
                        .select("x => x.concat(x)")
                        .select("x => x.concat(x)")
                        .select("x => x.concat(x)")
                        .select("x => x.concat(x)")
                        .select("x => x.concat(x)")
                        .select("x => x.concat(x)")
                        .select("x => x.concat(x)")
                        .select("x => x.concat(x)")
                        .select("x => x.concat(x)")
                        .select("x => x.concat(x)")
                        .selectMany();

                    return res.count(); ;
                },
                requires: 40960
            });

            test({ title: "Complex 11 - expressions & json",
                query: function(e) {
                    //criar um conjunto de objetos
                    var data = [];
                    e.each(function(x) { data.push({ index: x + (data.length % 500), result: x * x * x }) }, 5000);
                    //select result from index 3, 4 ,5
                    return data.toEnum().where("x => x.index >= 3").select("x => x.result").count();
                },
                requires: 24950
            });

            test({ title: "Complex 11.1 - expressions & json, distinct",
                query: function(e) {
                    //criar um conjunto de objetos
                    var data = [];
                    e.each(function(x) { data.push({ index: x + (data.length % 500), result: x * x * x }) }, 5000);
                    //select result from index 3, 4 ,5
                    return data
                        ._where("x => x.index >= 3")
                        .distinct("x=> x.index")
                        .select("x => x.result")
                        .count();
                },
                requires: 497
            });

            test({ title: "Array - add 100000 items",
                source: [],
                query: function(e) {
                    var list = [];
                    for (var i = 0; i < 100000; i++)
                        list.push(i);
                    return list.length;
                },
                requires: 100000
            });

            var list = new jMass.List();

            test({ title: "List - add 100000 items",
                source: [],
                query: function(e) {
                    for (var i = 0; i < 100000; i++)
                        list.add(i);
                    return list.length();
                },
                requires: 100000
            });

            test({ title: "List - findAll (odd numbers)",
                source: [],
                query: function(e) {
                    return list.findAll("x=> x%2 == 1").length();
                },
                requires: 50000
            });

            test({ title: "Expression class - x * 2",
                source: [],
                query: function(e) {
                    var e = new jMass.Expression("x=> x * 2");
                    return e.run(100);
                },
                requires: 200
            });

            test({ title: "Shortcut methods (_)",
                raw: true,
                query: function(e) {
                    return e
                    ._where("x=>x > 3")
                    ._select("x => x * 2")
                    ._sum();
                },
                requires: 18
            });

            test({ title: "Shortcut methods 2 (_)",
                raw: true,
                query: function(e) {
                    return e._sum();
                },
                requires: 15
            });

            test({ title: "Shortcut methods 3 (_)",
                raw: true,
                query: function(e) {
                    return e._avg();
                },
                requires: 3
            });


            var parents = [{ name: "john", age: 50, children: [{ name: "john2", age: 13 }, { name: "mia", age: 10}] },
                           { name: "mary", age: 45, children: [{ name: "mary2", age: 14}] },
                           { name: "paul", age: 30, children: []}];

            test({ title: "JSON - select parents with children",
                raw: true,
                source: parents,
                query: function(e) {
                    return e._where("x => x.children && x.children.length > 0").select("x=>x.name");
                },
                requires: "@result"
            });

            test({ title: "JSON - select average age of childs",
                raw: true,
                source: parents,
                query: function(e) {
                    return e._selectMany("x => x.children").avg("x => x.age");
                },
                requires: "@result"
            });

            parents = [{ name: "john", age: 50, children: [{ name: "john2", age: 13 }, { name: "mia", age: 10}] },
                       { name: "mary", age: 45, children: [{ name: "mary2", age: 14}] },
                       { name: "paul", age: 30, children: [{ name: "paul2", age: 12 }, { name: "paul3", age: 17 }, { name: "paul4", age: 17}]}];


            test({ title: "JSON - avg childs per parent",
                raw: true,
                source: parents,
                query: function(e) {
                    return e._avg("x => x.children.length");
                },
                requires: 2
            });

            test({ title: "Orderby - number",
                source: [3, 2, 1, 7, 6, 8, 10],
                query: function(e) {
                    return e._orderBy();
                },
                requires: [1, 2, 3, 6, 7, 8, 10]
            });

            test({ title: "Orderby - string",
                source: ["santos", "marcelo"],
                query: function(e) {
                    return e._orderBy();
                },
                requires: ["marcelo", "santos"]
            });

            test({ title: "Orderby - boolean",
                source: [true, false, true, false, true],
                query: function(e) {
                    return e._orderBy();
                },
                requires: [true, true, true, false, false]
            });

            test({ title: "Sort",
                source: ['marcelo', 1, "santos", false, 30, "zabumba", true],
                query: function(e) {
                    return e._sort();
                },
                requires: [1, 30, "marcelo", "santos", "zabumba", true, false]
            });

            test({ title: "Sort descending",
                source: ['marcelo', 1, "santos", false, 30, "zabumba", true],
                query: function(e) {
                    return e._sortDesc();
                },
                requires: [false, true, "zabumba", "santos", "marcelo", 30, 1]
            });

            test({ title: "Order by - parents by age",
                source: parents,
                query: function(e) {
                    return e._orderBy("p => p.age").select("p => p.name");
                },
                requires: ["paul", "mary", "john"]
            });
        });
        
    </script>

</head>
<body style="font-family: Tahoma, Verdana; font-size: 18px; color: Green;">
    <div id="bench">
    </div>
</body>
</html>
